package cn.lijiajia3515.cairo.system.modules.file2;

import cn.hutool.core.util.IdUtil;
import cn.lijiajia3515.cairo.core.CoreConstants;
import cn.lijiajia3515.cairo.core.exception.BusinessException;
import cn.lijiajia3515.cairo.core.exception.UnknownBusinessException;
import cn.lijiajia3515.cairo.system.framework.minio.MinioProperties;
import cn.lijiajia3515.cairo.system.modules.file.FileBusiness;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.MinioClient;
import io.minio.PostPolicy;
import io.minio.PutObjectArgs;
import io.minio.StatObjectArgs;
import io.minio.errors.ErrorResponseException;
import io.minio.errors.InsufficientDataException;
import io.minio.errors.InternalException;
import io.minio.errors.InvalidResponseException;
import io.minio.errors.ServerException;
import io.minio.errors.XmlParserException;
import io.minio.http.Method;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * minio 对象操作
 */
@Service
@Slf4j
public class File2Service {
	/**
	 * 临时 文件存储 前缀
	 */
	private static final String TEMPORARY_PREFIX = "temporary/";
	/**
	 * minio client
	 */
	private final MinioClient client;
	/**
	 * 端点
	 */
	private final String endpoint;

	/**
	 * 存储桶
	 */
	private final String bucket;

	public File2Service(MinioClient client, MinioProperties properties) {
		this.client = client;
		this.endpoint = properties.getEndpoint();
		this.bucket = properties.getBucket();
	}

	public String getObjectUrl(String object) throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, ErrorResponseException {
		clearObject(object);
		return client.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().bucket(bucket).object(object).method(Method.GET).expiry(2, TimeUnit.HOURS).build());
	}

	public List<String> getObjectUrl(GetObjectsUrlParam param) {
		int secondExpiry = (int) Optional.ofNullable(param.getTtl()).orElse(Duration.ofHours(1)).toSeconds();
		return param.getKeys().stream()
			.map(this::clearObject)
			.map(object -> {
				object = clearObject(object);
				try {
					return client.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().bucket(bucket).object(object).method(Method.GET).expiry(secondExpiry, TimeUnit.SECONDS).build());
				} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | XmlParserException | ServerException e) {
					log.info("文件存储服务异常", e);
					// throw new UnknownBusinessException("文件存储服务异常");
					return "unknown";
				}
			}).collect(Collectors.toList());
	}

	public SignedArgs getUploadObjectPresignedArgs(SignFile2Param params) {
		ZonedDateTime time = ZonedDateTime.now().plus(params.getTimeToLive());
		PostPolicy policy = new PostPolicy(bucket, time);
		String keyPrefix = Optional.ofNullable(params.getKeyPrefix()).orElse("file/");
		policy.addStartsWithCondition("key", keyPrefix);
		Optional.ofNullable(params.getMeta())
			.orElse(Collections.emptyMap())
			.forEach((k, v) -> policy.addStartsWithCondition(String.format("x-amz-meta-%s", k.toLowerCase(Locale.ROOT)), v));
		Map<String, String> formData;
		try {
			formData = client.getPresignedPostFormData(policy);
		} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | XmlParserException | ServerException e) {
			log.info("文件存储服务异常", e);
			throw new UnknownBusinessException("文件存储服务异常");
		}
		return SignedArgs.builder().endpoint(endpoint).bucket(bucket).expiresTime(time.toLocalDateTime()).keyPrefix(keyPrefix).signPostFormData(formData).build();
	}

	/**
	 * 查询文件状态
	 *
	 * @param param 参数
	 * @return 文件信息
	 */
	public List<FileStat> findStat(StatFileParam param) {
		final StatObjectArgs.Builder builder = StatObjectArgs.builder();
		return param.getObjects().stream().map(x -> {
			final StatObjectArgs args = builder.bucket(bucket).object(x).build();
			try {
				return FileConverter.convert(client.statObject(args));
			} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
				log.debug("错误", e);
				return FileStat.builder()
					.bucket(bucket)
					.object(x).lastModified(LocalDateTime.now())
					.size(0L).
					userMetadata(Collections.emptyMap())
					.build();
			}
		}).collect(Collectors.toList());
	}

	public List<String> getSignUrl(List<String> objects) {
		return objects.stream()

			.map(object -> {
				object = clearObject(object);
				try {
					return client.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().bucket(bucket).object(object).method(Method.PUT).expiry(2, TimeUnit.HOURS).build());
				} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | XmlParserException | ServerException e) {
					log.info("获取文件上传url失败", e);
					return "unknown";
				}
			}).collect(Collectors.toList());
	}

	public List<List<String>> getTemporarySignUrl(Integer size) {
		return IntStream.range(0, size).parallel().mapToObj(x -> {
			String object = TEMPORARY_PREFIX.concat(IdUtil.objectId());
			try {
				return Arrays.asList(object, client.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().bucket(bucket).object(object).method(Method.PUT).expiry(2, TimeUnit.HOURS).build()));
			} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | XmlParserException | ServerException e) {
				log.info("文件存储服务异常", e);
				return Arrays.asList(object, "unknown");
			}
		}).collect(Collectors.toList());
	}

	/**
	 * 文件上传
	 *
	 * @param client client 上传 client
	 * @param path   path 上传路径
	 * @param files  files 上传file
	 * @return file 二位数组, 下标0, 对象key值, 下标1 访问url
	 */
	List<List<String>> uploadObjects(String client, String path, Collection<MultipartFile> files) {
		return files.parallelStream()
			.map(file -> {
				String filename = Optional.ofNullable(file.getOriginalFilename()).orElse(CoreConstants.SNOWFLAKE.nextIdStr());
				String contentType = file.getContentType();

				try (InputStream in = file.getInputStream()) {
					String object = path.concat("/").concat(filename);
					final PutObjectArgs.Builder argsBuilder = PutObjectArgs.builder();
					argsBuilder.stream(in, -1, 1024 * 1024 * 100);

					argsBuilder.object(object)
						.contentType(contentType)
						.userMetadata(new HashMap<>() {{
							put("Client", client);
							put("Uid", "Uid");
						}})
						.bucket(bucket);
					this.client.putObject(argsBuilder.build());

					final String presignedObjectUrl = this.client.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
						.bucket(bucket)
						.object(object)
						.expiry(1, TimeUnit.DAYS)
						.method(Method.GET)
						.build());
					return Arrays.asList(object, presignedObjectUrl);
				} catch (IOException | ServerException | InsufficientDataException | ErrorResponseException | NoSuchAlgorithmException | InvalidKeyException | InvalidResponseException | XmlParserException | InternalException e) {
					throw new BusinessException("文件上传失败", e, FileBusiness.UploadField);
				}
			})
			.collect(Collectors.toList());
	}

	private String clearObject(String object) {
		if (object.startsWith("/")) {
			object = object.replaceFirst("/", "");
		}
		return object;
	}
}
